package net.kem.android.positioner;

import java.io.IOException;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import net.kem.android.positioner.R;

import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Point;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

/**
 * This example is mostly taken from
 * http://www.codeproject.com/Articles/112044/positioner
 * -App-to-Find-Current-Nearest-Location-us
 *
 * @author Evgeny.Kurtser
 *
 */
public class PositionerActivity extends MapActivity {
	// private static final String TEMPLATE_REQUEST_OLD =
	// "{\"action\": {"
	// + "\"id\": \"%d\","
	// + "\"from\": \"%s\","
	// + "\"geoFix\": {\"lat\": \"%d\", \"lon\": \"%d\", \"alt\": \"%d\"}"
	// + "}}";
	// private static final int ACTION_GEOFIX = 10;
	private static final String TEMPLATE_REQUEST = "%d;%s;%d;%s;%1.6f;%1.6f\r\n";

	private static final String PROX_ALERT_INTENT = "android.intent.action.PROXIMITY_ALERT";

	private static final float POINT_RADIUS = 50;
	private static final long PROX_ALERT_EXPIRATION = 5000L;

	private MapView mapView;
	private MapController mapController;

	private LocationManager locationManager;
	private LocationListener locationListener;
	private BroadcastReceiver proximityReceiver;
	private UIHandler uiHandler;

	private Location currentLocation;// GeoPoint
	private String currentAddress;

	private ServerCall tServerCall;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		Settings.init(this); // Initialize Setting instance.

		mapView = (MapView) findViewById(R.id.mapView);
		mapView.setStreetView(true); // enable Street view by default
		// mapView.setSatellite(true); // enable to show Satellite view
		// mapView.setTraffic(true); // enable to show Traffic on map
		mapView.setBuiltInZoomControls(true);
		mapController = mapView.getController();

		// Move to pre-defined location.
		String coordinates[] = { "32.2", "34.5" };
		double lat = Double.parseDouble(coordinates[0]);
		double lng = Double.parseDouble(coordinates[1]);
		currentLocation = new Location(LOCATION_SERVICE);
		mapController.animateTo(new GeoPoint((int) (lat * 1E6), (int) (lng * 1E6)));

		mapController.setZoom(10); // Set default zoom

		tServerCall = new ServerCall();
		tServerCall.start();

		// Work with GPS service
		locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		locationListener = new GPSLocationListener();
		long gpsRefreshInterval = Settings.getGPSRefreshInterval() * 1000L;
		float gpsRefreshDistance = Settings.getGPSRefreshDistance();
		//Settings.getGPSRefreshDistance();//TODO Remove
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, gpsRefreshInterval, gpsRefreshDistance, locationListener); // GPS service will notify me about new location every 30 seconds or if I move more than 3 meters.
		//LocationManager.NETWORK_PROVIDER // Can also be used.

		proximityReceiver = new ProximityIntentReceiver();
		registerReceiver(proximityReceiver, new IntentFilter(PROX_ALERT_INTENT));

		Map<String, POI>pois = Settings.getPOIs();
		for(POI poi: pois.values()) {
			if(poi.isDefined()) {
				PendingIntent proximityIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(PROX_ALERT_INTENT), 0);//this
				poi.setProximityIntent(proximityIntent);
				addProximityAlert(poi.getCoordinates().getLatitude(), poi.getCoordinates().getLongitude(), proximityIntent);
			}
		}

		// Define map overlay
		MapGestureDetectorOverlay mapOverlay = new MapGestureDetectorOverlay();//new MyOnGestureListener()
		List<Overlay> listOfOverlays = mapView.getOverlays();
		listOfOverlays.clear();
		listOfOverlays.add(mapOverlay);

		registerForContextMenu(mapView);

		setAuthentication();

		// Toast within a thread.
		HandlerThread uiThread = new HandlerThread("UIHandler");
		uiThread.start();
		uiHandler = new UIHandler(uiThread.getLooper());
	}

	//onUserInteraction(); See http://stackoverflow.com/questions/7743976/android-activity-manager/7744354#7744354
	// http://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/

	@Override
	protected void onStart() {
		super.onStart();
		// The activity is about to become visible.
	}
	@Override
	protected void onResume() {
		super.onResume();
		// The activity has become visible (it is now "resumed").
	}
	@Override
	protected void onPause() {
		super.onPause();
		// Another activity is taking focus (this activity is about to be "paused").
	}
	@Override
	protected void onStop() {
		super.onStop();
		// The activity is no longer visible (it is now "stopped")
	}
	@Override
	protected void onDestroy() {
		super.onDestroy();
		tServerCall.interrupt();
		locationManager.removeUpdates(locationListener);
		unregisterForContextMenu(mapView);
		unregisterReceiver(proximityReceiver);
		// The activity is about to be destroyed.
	}

	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}

	// ---- Context Menu BEGIN ----
	@Override
	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
		super.onCreateContextMenu(menu, v, menuInfo);
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.context, menu);
	}

	@Override
	public boolean onContextItemSelected(MenuItem item) {
		// AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
		POI poi = Settings.getById(item.getItemId());
		if(poi != null) {
			Log.d("onContextItemSelected", poi.getName() + " at " + currentLocation.toString());
			poi.setCoordinates(currentLocation);

			PendingIntent proximityIntent = poi.getProximityIntent();
			if(proximityIntent != null) {
				locationManager.removeProximityAlert(proximityIntent);
			} else {
				proximityIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(PROX_ALERT_INTENT), 0);//this
				poi.setProximityIntent(proximityIntent);
			}
			addProximityAlert(currentLocation.getLatitude(), currentLocation.getLongitude(), proximityIntent);

			poi.store();
			return true;
		} else {
			return super.onContextItemSelected(item);
		}
	}

	// ---- Context Menu END ----

	// ---- System Menu BEGIN ----
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		super.onPrepareOptionsMenu(menu);
		MenuItem item;
		item = menu.findItem(R.id.itemHome);
		item.setEnabled(Settings.isPOIDefined("Home"));
		item = menu.findItem(R.id.itemWork);
		item.setEnabled(Settings.isPOIDefined("Work"));
		item = menu.findItem(R.id.itemClub);
		item.setEnabled(Settings.isPOIDefined("Club"));
		item = menu.findItem(R.id.itemGym);
		item.setEnabled(Settings.isPOIDefined("Gym"));
		item = menu.findItem(R.id.itemPub);
		item.setEnabled(Settings.isPOIDefined("Pub"));
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle item selection
		String key;
		int locationId;
		switch (item.getItemId()) {
		case R.id.itemHome:
			key = "Home";
			locationId = 1;
			break;
		case R.id.itemWork:
			key = "Work";
			locationId = 2;
			break;
		case R.id.itemClub:
			key = "Club";
			locationId = 3;
			break;
		case R.id.itemGym:
			key = "Gym";
			locationId = 4;
			break;
		case R.id.itemPub:
			key = "Pub";
			locationId = 5;
			break;
		case R.id.itemStoreCurrentLocation:
			openContextMenu(mapView);
			return true;
		case R.id.itemAbout:
			new AlertDialog.Builder(this)
			.setTitle("About")
			.setMessage( "GPS location sample application.\nBy Evgeny Kurtser\nlopotun@gmail.com" )
			.setPositiveButton("OK", new OnClickListener() {
				public void onClick(DialogInterface arg0, int arg1) {////do stuff onclick of YES
					arg0.dismiss();
				}
			}).show();
			return true;
		case R.id.itemLogin:
			Intent i = new Intent(PositionerActivity.this, SettingsActivity.class);
			startActivity(i);
			return true;
		case R.id.itemExit:
			finish();
			return true;
		default:
			return super.onOptionsItemSelected(item);
		}
		POI poi = Settings.getPOIByName(key);
		if(poi != null) {
			talkToServer(locationId, (float)(poi.getCoordinates().getLatitude()), (float)(poi.getCoordinates().getLongitude()));
		}
		return true;
	}
	// ---- System Menu END ----

	private void toast(String msg, int duration) {
		Toast toast= Toast.makeText(getApplicationContext(), msg, duration);
		toast.setGravity(Gravity.TOP|Gravity.CENTER, 0, 20);
		toast.show();
	}

	private void addProximityAlert(double latitude, double longitude, PendingIntent proximityIntent) {
		locationManager.addProximityAlert(latitude, // the latitude of the central point of the alert region
				longitude, // the longitude of the central point of the alert region
				POINT_RADIUS, // the radius of the central point of the alert region, in meters
				PROX_ALERT_EXPIRATION, // time for this proximity alert, in milliseconds, or -1 to indicate no expiration
				proximityIntent // will be used to generate an Intent to fire when entry to or exit from the alert region is detected
				);
	}

	private GeoPoint locationToGeoPoint(Location location) {
		GeoPoint res = new GeoPoint((int)(location.getLatitude()*1E6), (int)(location.getLongitude()*1E6));
		return res;
	}
	private Location geoPointToLocation(GeoPoint geoPoint) {
		Location res = new Location("Converter");
		res.setLatitude(geoPoint.getLatitudeE6()/1E6);
		res.setLongitude(geoPoint.getLongitudeE6()/1E6);
		return res;
	}

	private String convertPointToLocation(GeoPoint point) {
		String address = null;
		Geocoder geoCoder = new Geocoder(getBaseContext(), Locale.ENGLISH);// Locale.getDefault()
		try {
			List<Address> addresses = geoCoder.getFromLocation(point.getLatitudeE6() / 1E6, point.getLongitudeE6() / 1E6, 1);
			if (addresses.size() > 0) {
				address = "";
				for (int index = 0; index < addresses.get(0).getMaxAddressLineIndex(); index++)
					address += addresses.get(0).getAddressLine(index) + " ";
			}
		} catch (IOException e) {
			Log.e("convertPointToLocation", "Could not get location address: " + e.getMessage());
		}
		return address;
	}

	private void talkToServer(final int locationId, final Float lat, final Float lon) {
		tServerCall.sendToServer(locationId, lat, lon);
	}

	private class ServerCall extends Thread {
		private int locationId = -1;
		private Float lat, lon;

		ServerCall() {
			super("ServerCall");
			setDaemon(true);
		}

		void sendToServer(final int locationId, final Float lat, final Float lon) {
			this.locationId = locationId;
			this.lat = lat;
			this.lon = lon;
			synchronized (this) {
				notify();
			}
		}

		public void run() {
			while(true) {
				if(locationId >= 0) {
					String username = Settings.getUsername();
					String host = Settings.getHost();
					int port = Settings.getPort();
					int requestId = 1;
					String payload = String.format(TEMPLATE_REQUEST, requestId, username, locationId, currentAddress == null ? "": currentAddress, lat, lon);
					Log.d("talkToServer", payload);
					try {
						String response = TalkToServer.talkToServer(host, port, payload, true);
						handleUIRequest("Sent. Resp: " + response);
					} catch (UnknownHostException e) {
						Log.e("talkToServer", "Unknown host " + host);
						handleUIRequest("Unknown host " + host);
					} catch (IOException e) {
						Log.e("talkToServer", "Send error " + e.getMessage());
						handleUIRequest("Send error " + e.getMessage());
					}
				}
				synchronized (this) {
					try {
						wait();
					} catch (InterruptedException e) {
						Log.v("ServerCall", "interrupted");
						break;
					}
				}
			}
		}

	}

	private static void setAuthentication() {
		final String username = Settings.getUsername();
		final String password = Settings.getPassword();
		Authenticator.setDefault(new Authenticator() {
			protected PasswordAuthentication getPasswordAuthentication() {
				return new PasswordAuthentication(username, password.toCharArray());
			}
		});
	}

	private class GPSLocationListener implements LocationListener {
		public void onLocationChanged(Location location) {
			if (location != null) {
				currentLocation = location;
				GeoPoint point = new GeoPoint((int) (location.getLatitude() * 1E6), (int) (location.getLongitude() * 1E6));
				currentAddress = convertPointToLocation(point);

				// add marker
				List<Overlay> listOfOverlays = mapView.getOverlays();
				MapGestureDetectorOverlay mapOverlay = (MapGestureDetectorOverlay) listOfOverlays.get(0);
				mapOverlay.setPointToDraw(point);

				Log.d("onLocationChanged", currentAddress + "@" + currentLocation.getLatitude() + "X" + currentLocation.getLongitude());
				mapController.animateTo(point);
				mapView.invalidate();

				if(!isWithinRadius(Settings.getPOIRadius())) {
					talkToServer(0, (float)currentLocation.getLatitude(), (float)currentLocation.getLongitude());
				}
			}
		}

		private boolean isWithinRadius(int radius) {
			boolean res;
			Map<String, POI>pois = Settings.getPOIs();
			for(POI poi: pois.values()) {
				res = isWithinRadius(poi.getCoordinates(), radius);
				if(res) {
					String poiName = poi.getName();
					Log.d("withinRadius", "Inside " + radius + " meters from " + poiName);
					return true;
				}
			}
			Log.d("withinRadius", "Out of " + radius + " meters from all known POIs");
			return false;
		}
		private boolean isWithinRadius(String poiName, int radius) {
			POI poi = Settings.getPOIByName(poiName);
			if(poi != null) {
				if(isWithinRadius(poi.getCoordinates(), radius)) {
					Log.d("withinRadius", "Inside " + radius + " meters from " + poiName);
					return true;
				}
				Log.d("withinRadius", "Out of " + radius + " meters from all known POIs");
			}
			return false;
		}
		private boolean isWithinRadius(Location location, int radius) {
			if(location != null) {
				float distance = currentLocation.distanceTo(location);
				if(distance < radius) {
					return true;
				}
//				float lat = (float)(poi.getCoordinates().getLatitudeE6()/1E6);
//				float lon = (float)(poi.getCoordinates().getLongitudeE6()/1E6);
//				if(lat > 0 && lon > 0) {
//					if(Math.abs(currentLocation.getLatitude() - lat) < radius) {
//						if(Math.abs(currentLocation.getLongitude() - lon) < radius) {
//							return true;
//						}
//					}
//				}
			}
			return false;
		}

		public void onProviderDisabled(String provider) {
			Log.i("GPS status", "ProviderDisabled: " + provider);
		}

		public void onProviderEnabled(String provider) {
			Log.i("GPS status", "ProviderEnabled: " + provider);
		}

		public void onStatusChanged(String provider, int status, Bundle extras) {
			Log.i("GPS status", "Status of provider " + provider + " has changed to " + status);
		}
	}

	class MapGestureDetectorOverlay extends Overlay implements OnGestureListener {
		private GeoPoint pointToDraw;
//		private boolean isMoving;
		// ----
		private GestureDetector gestureDetector;
		private OnGestureListener onGestureListener;

		public MapGestureDetectorOverlay() {
			gestureDetector = new GestureDetector(this);
		}

		public MapGestureDetectorOverlay(OnGestureListener onGestureListener) {
			this();
			setOnGestureListener(onGestureListener);
		}

		@Override
		public boolean onTouchEvent(MotionEvent event, MapView mapView) {
			if (gestureDetector.onTouchEvent(event)) {
				return true;
			}
/*
			// ---when user lifts his finger---
			if (event.getAction() == MotionEvent.ACTION_UP) {
				pointToDraw = mapView.getProjection().fromPixels((int) event.getX(), (int) event.getY());
				currentAddress = convertPointToLocation(pointToDraw);
				if (!isMoving) {
					long downPressed = event.getDownTime();
					long upPressed = event.getEventTime();
					if (upPressed - downPressed > 1000L) { // This is a long tap.
						currentLocation.setLatitude(pointToDraw.getLatitudeE6() / 1E6);
						currentLocation.setLongitude(pointToDraw.getLongitudeE6() / 1E6);
						openContextMenu(mapView);
					} else {
						Toast.makeText(getBaseContext(), currentAddress, Toast.LENGTH_SHORT).show();
					}
				} else {
					isMoving = false;
				}
			}
			if (event.getAction() == MotionEvent.ACTION_MOVE) {
				isMoving = true;
			}
			*/
			return false;
		}

		@Override
		public boolean onDown(MotionEvent e) {
			if (onGestureListener != null) {
				return onGestureListener.onDown(e);
			}
			return false;
		}

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
			if (onGestureListener != null) {
				return onGestureListener.onFling(e1, e2, velocityX, velocityY);
			}
			return false;
		}

		@Override
		public void onLongPress(MotionEvent event) {
			if (onGestureListener != null) {
				onGestureListener.onLongPress(event);
			}
			pointToDraw = mapView.getProjection().fromPixels((int) event.getX(), (int) event.getY());
			currentAddress = convertPointToLocation(pointToDraw);
			if(currentAddress != null) {
				toast(currentAddress, Toast.LENGTH_LONG);
			}
		}

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
			if (onGestureListener != null) {
				onGestureListener.onScroll(e1, e2, distanceX, distanceY);
			}
			return false;
		}

		@Override
		public void onShowPress(MotionEvent e) {
			if (onGestureListener != null) {
				onGestureListener.onShowPress(e);
			}
		}

		@Override
		public boolean onSingleTapUp(MotionEvent event) {
			if (onGestureListener != null) {
				onGestureListener.onSingleTapUp(event);
			}
			pointToDraw = mapView.getProjection().fromPixels((int) event.getX(), (int) event.getY());
			currentAddress = convertPointToLocation(pointToDraw);
			currentLocation.setLatitude(pointToDraw.getLatitudeE6() / 1E6);
			currentLocation.setLongitude(pointToDraw.getLongitudeE6() / 1E6);
			openContextMenu(mapView);
			return false;
		}

		public boolean isLongpressEnabled() {
			return gestureDetector.isLongpressEnabled();
		}

		public void setIsLongpressEnabled(boolean isLongpressEnabled) {
			gestureDetector.setIsLongpressEnabled(isLongpressEnabled);
		}

		public OnGestureListener getOnGestureListener() {
			return onGestureListener;
		}

		public void setOnGestureListener(OnGestureListener onGestureListener) {
			this.onGestureListener = onGestureListener;
		}

		// ----
		void setPointToDraw(GeoPoint point) {
			pointToDraw = point;
		}

		@Override
		public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) {
			super.draw(canvas, mapView, shadow);
			Map<String, POI>pois = Settings.getPOIs();
			for(POI poi: pois.values()) {
				drawOnMap(canvas, locationToGeoPoint(poi.getCoordinates()), poi.getResourceDrawable());
			}
			if (pointToDraw != null) {
				drawOnMap(canvas, pointToDraw, R.drawable.redflag);
				// convert point to pixels
				Point screenPts = new Point();
				mapView.getProjection().toPixels(pointToDraw, screenPts);

				// add marker
				Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.redflag);
				canvas.drawBitmap(bmp, screenPts.x, screenPts.y - 24, null);
				return true;
			} else {
				return false;
			}
		}

		private void drawOnMap(Canvas canvas, GeoPoint coords, int resourceDrawable) {
			// convert point to pixels
			Point screenPts = new Point();
			mapView.getProjection().toPixels(coords, screenPts);

			// add marker
			Bitmap bmp = BitmapFactory.decodeResource(getResources(), resourceDrawable);
			canvas.drawBitmap(bmp, screenPts.x, screenPts.y - 24, null);
		}

		/*
		 * @Override public boolean onTap(GeoPoint p, MapView mapView) {
		 * //pointToDraw = mapView.getProjection().fromPixels((int)
		 * event.getX(), (int) event.getY()); pointToDraw = p; currentAddress =
		 * convertPointToLocation(p);
		 * currentLocation.setLatitude(pointToDraw.getLatitudeE6() / 1E6);
		 * currentLocation.setLongitude(pointToDraw.getLongitudeE6() / 1E6);
		 * openContextMenu(mapView); return true; }
		 */
	}

	private final class UIHandler extends Handler {
		public static final int DISPLAY_UI_TOAST = 0;
		public static final int DISPLAY_UI_DIALOG = 1;

		public UIHandler(Looper looper) {
			super(looper);
		}

		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case UIHandler.DISPLAY_UI_TOAST: {
				Context context = getApplicationContext();
				Toast t = Toast.makeText(context, (String) msg.obj, Toast.LENGTH_LONG);
				t.show();
			}
			case UIHandler.DISPLAY_UI_DIALOG:
				// TBD
			default:
				break;
			}
		}
	}

	protected void handleUIRequest(String message) {
		Message msg = uiHandler.obtainMessage(UIHandler.DISPLAY_UI_TOAST);
		msg.obj = message;
		uiHandler.sendMessage(msg);
	}

}
